package com.such.kit.ftp;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.tools.ant.util.ReaderInputStream;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.Retry;
import com.such.kit.file.FileCommons;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.bean.FileOrder;
import com.such.kit.ftp.support.Ftp;
import com.such.kit.stream.StreamCommons;
import com.such.kit.validate.ValidateCommons;

public class FtpCommons extends Ftp {

	/** DATA_CONNECTION_MODE_ACTIVE：主动模式 */
	public static final String DATA_CONNECTION_MODE_ACTIVE = "ACTIVE";
	/** DATA_CONNECTION_MODE_PASSIVE：被动模式 */
	public static final String DATA_CONNECTION_MODE_PASSIVE = "PASSIVE";

	private FTPClient ftpClient;
	private String controlEncoding;
	private String dataConnectionMode = DATA_CONNECTION_MODE_PASSIVE;

	/**
	 * <pre>
	 * 设置 FTP 额外参数：
	 * controlEncoding [参考 {@link FTPClient#setControlEncoding(String)}]
	 * dataConnectionMode [{@link #DATA_CONNECTION_MODE_ACTIVE} | {@link #DATA_CONNECTION_MODE_PASSIVE}] [参考 {@link FTPClient#enterLocalPassiveMode()}]
	 * </pre>
	 * @see com.such.kit.ftp.support.Ftp#setProperty(java.lang.String, java.lang.Object)
	 */
	public void setProperty(String key, Object value) {
		if ("controlEncoding".equals(key)) {
			this.controlEncoding = (String) value;
		} else if ("dataConnectionMode".equals(key)) {
			this.dataConnectionMode = (String) value;
		}
	}

	public boolean connect(final String ip, final int port, String username, String password) throws Exception {
		new Retry<String, Boolean>() {
			public Boolean getObject(String param) throws Exception {
				ftpClient = new FTPClient();
				if (ValidateCommons.isNotEmpty(controlEncoding)) {
					ftpClient.setControlEncoding(controlEncoding);
				}
				ftpClient.connect(ip, port);
				return true;
			}
		}.doRetry();
		boolean boole = this.ftpClient.login(username, password);
		if (boole) {
			int replyCode = this.ftpClient.getReplyCode();
			if (FTPReply.isPositiveCompletion(replyCode)) {
				this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
				if (DATA_CONNECTION_MODE_PASSIVE.equals(dataConnectionMode)) {
					this.ftpClient.enterLocalPassiveMode();
				}
			} else {
				Logger.warn(getClass(), "FTP 服务器返回非正常代码，断开连接 ...");
				this.logout();
				boole = false;
			}
		}
		return boole;
	}

	public void logout() throws Exception {
		if (ValidateCommons.isNotEmpty(this.ftpClient) && this.ftpClient.isConnected()) {
			this.ftpClient.logout();
			this.ftpClient.disconnect();
		}
	}

	public boolean rename(String ftpPath, String newName) throws Exception {
		final String oldPath = FileCommons.changeCompatSeparator(ftpPath);
		final String newPath = FileCommons.appendPathNode(FileCommons.cutLastPathNode(ftpPath), newName);
		deleteFile(newPath);
		return new Retry<String, Boolean>() {
			public Boolean getObject(String param) throws Exception {
				return ftpClient.rename(encodeURI(oldPath), encodeURI(newPath));
			}
		}.doRetry();
	}

	public void createFolder(String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		String path = FileCommons.cutLastPathNode(ftpPath);
		if (ValidateCommons.isNotEmpty(path)) {
			createFolder(encodeURI(path));
		}
		new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				ftpClient.mkd(encodeURI(ftpPath));
				return true;
			}
		}.doRetry();
	}

	public void deleteFolder(String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		deleteFiles(ftpPath, true);
		new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				ftpClient.rmd(encodeURI(ftpPath));
				return true;
			}
		}.doRetry();
	}

	public void downloadFolder(String ftpPath, ZipOutputStream out) throws Exception {
		downloadFolder(ftpPath, "", out);
	}

	public void downloadFolder(String ftpPath, String zipParentPath, ZipOutputStream out) throws Exception {
		downloadFiles(listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false), zipParentPath, out);
	}

	public List<FileInfo> listFolder(String ftpPath, final String orderMode, final boolean isDesc) throws Exception {
		List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
		if (ValidateCommons.isNotEmpty(ftpPath)) {
			ftpPath = FileCommons.changeCompatSeparator(ftpPath);
			fileInfoList = new Retry<String, List<FileInfo>>(ftpPath) {
				public List<FileInfo> getObject(String ftpPath) throws Exception {
					List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
					FTPFile[] files = ftpClient.listFiles(encodeURI(ftpPath));
					if (ValidateCommons.isNotEmpty(files)) {
						for (FTPFile file : files) {
							String name = new String(file.getName().getBytes(ftpCharset), localCharset);
							String path = FileCommons.appendPathNode(ftpPath, name);
							FileInfo fb = new FileInfo(path, file.getSize(), file.getTimestamp().getTime());
							fb.setAttribute(file.isDirectory(), false, true, false, false);
							if (ValidateCommons.isEmpty(fileFilter) || fileFilter.accept(fb)) {
								fileInfoList.add(fb);
							}
						}
						Collections.sort(fileInfoList, new FileOrder(orderMode, isDesc));
					}
					return fileInfoList;
				}
			}.doRetry();
		}
		return fileInfoList;
	}

	public boolean isFolder(String ftpPath) throws Exception {
		if (ValidateCommons.isEmpty(ftpPath)) {
			throw new Exception("指定的路径为空");
		}
		List<FileInfo> fileInfoList = listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false);
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				if (fb.getName().equals(FileCommons.getLastPathNode(ftpPath))) {
					if (fb.isDirectory()) {
						return true;
					} else {
						break;
					}
				}
			}
		}
		return false;
	}

	public boolean createFile(final InputStream localIn, String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		String path = FileCommons.cutLastPathNode(ftpPath);
		if (ValidateCommons.isNotEmpty(path)) {
			createFolder(encodeURI(path));
		}
		boolean boole = false;
		try {
			boole = new Retry<String, Boolean>(ftpPath) {
				public Boolean getObject(String ftpPath) throws Exception {
					return ftpClient.storeFile(encodeURI(ftpPath), localIn);
				}
			}.doRetry();
		} finally {
			StreamCommons.closePure(localIn);
		}
		return boole;
	}

	public boolean createFile(Reader localR, String ftpPath) throws Exception {
		return createFile(new ReaderInputStream(localR), ftpPath);
	}

	public boolean deleteFile(String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				return ftpClient.deleteFile(encodeURI(ftpPath));
			}
		}.doRetry();
	}

	public void deleteFiles(String ftpPath, boolean containFolder) throws Exception {
		deleteFiles(listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false), containFolder);
	}

	public void deleteFiles(List<FileInfo> fileInfoList, boolean containFolder) throws Exception {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				String ftpPath = fb.getPath();
				if (fb.isDirectory() && containFolder) {
					deleteFolder(ftpPath);
				} else {
					deleteFile(ftpPath);
				}
			}
		}
	}

	public <T> T downloadFile(String ftpPath, final Callable<InputStream, T> callable) throws Exception {
		return new Retry<String, T>(ftpPath) {
			public T getObject(String ftpPath) throws Exception {
				T returnObj = null;
				InputStream in = ftpClient.retrieveFileStream(encodeURI(ftpPath));
				try {
					returnObj = callable.call(in);
				} finally {
					StreamCommons.close(in);
					ftpClient.completePendingCommand();
				}
				return returnObj;
			}
		}.doRetry();
	}

	public boolean downloadFile(String ftpPath, final OutputStream out) throws Exception {
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				return ftpClient.retrieveFile(encodeURI(ftpPath), out);
			}
		}.doRetry();
	}

	public void downloadFiles(List<FileInfo> fileInfoList, ZipOutputStream out) throws Exception {
		downloadFiles(fileInfoList, "", out);
	}

	public void downloadFiles(List<FileInfo> fileInfoList, String zipParentPath, ZipOutputStream out) throws Exception {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				String filename = fb.getName();
				if (ValidateCommons.isNotEmpty(zipParentPath)) {
					filename = FileCommons.appendPathNode(zipParentPath, filename);
				}
				String ftpPath = fb.getPath();
				if (fb.isDirectory()) {
					downloadFolder(ftpPath, filename, out);
				} else {
					ZipEntry entry = new ZipEntry(filename);
					out.putNextEntry(entry);
					downloadFile(ftpPath, out);
				}
			}
		}
	}

	public boolean transferFile(String ftpPath, final Ftp targetFtp, final String targetPath) throws Exception {
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				boolean boole = false;
				InputStream in = ftpClient.retrieveFileStream(encodeURI(ftpPath));
				try {
					boole = targetFtp.createFile(in, targetPath);
				} finally {
					StreamCommons.close(in);
					ftpClient.completePendingCommand();
				}
				return boole;
			}
		}.doRetry();
	}

}
